<script setup>
import {ref, nextTick} from 'vue'
import {storeToRefs} from "pinia";
import {useUserProfile} from '@/stores/useUserProfile.js'
import {ArchiveTwotone} from '@vicons/material'
import {
	generateFrame,
	ready,
	dataWrite,
	softwareUpdate,
	stopRefactorByModule
} from "@/utils/refactor/index.js";
import {NText, useMessage, useNotification} from "naive-ui";
import {getTimestampFilename} from "@/utils/getDateTime.js";
import AutoModal from "@/views/refactor/components/AutoModal.vue";


// 自动重构界面模态框显示状态
const showModal = ref(false);
// 是否处于遮罩状态
const isOverlay = ref(true)
const notification = useNotification()
const userProfile = useUserProfile()
const {profile, latestTelemetryData} = storeToRefs(userProfile)

// 文件二进制blob对象
const combinedArray = ref(null)
// 进度
const percentage = ref(0)
// 延时
const delay = ref(1.7)
// 字节数
const chunkSize = ref(264)
// 重构超时设置
const timeout = ref(30)
// 目的地址
const targetAddr = ref("0x44")

/**
 * 渲染选项时后方加入十六进制
 * @param option
 * @returns {any}
 */
const renderLabel = (option) => {
	return h(
		NText,
		{},
		`${option.label}(${option.value})`
	)
}
// 文件分区标识
const filePartition = ref("user")
// 写入类型
const writeType = ref("0x55")
// 加载类型
const loadType = ref("0x11")
// 文件大小
const fileLength = ref(0)
// 两个串口对象
const refactor = computed(() => profile.value.tabList.find(tab => tab.asLabel === "refactor"))
const optical = computed(() => profile.value.tabList.find(tab => tab.asLabel === "optical"))

// 目的地址选项
const targetAddrOptions = [
	{
		label: "光学头1主控",
		type: "主控模块",
		value: "0x44"
	},
	{
		label: "光学头1相机",
		type: "相机模块",
		value: "0x55"
	},
	{
		label: "光学头1星敏",
		type: "星敏模块",
		value: "0x66"
	},
	{
		label: "光学头2主控",
		type: "主控模块",
		value: "0x77"
	},
	{
		label: "光学头2相机",
		type: "相机模块",
		value: "0x88"
	},
	{
		label: "光学头2星敏",
		type: "星敏模块",
		value: "0x99"
	},
	{
		label: "光学头1电机",
		type: "电机模块",
		value: "0xDD"
	},
	{
		label: "光学头2电机",
		type: "电机模块",
		value: "0xEE"
	},
]
// 重构模块
const module = ref(targetAddrOptions.find(item => item.value === targetAddr.value).type)
// 写入选项
const writeOptions = [
	{
		label: "主控user区域",
		value: "0x11"
	},
	{
		label: "主控golden区域",
		value: "0x22"
	},
	{
		label: "主控spidir区域",
		value: "0x33"
	},
	{
		label: "写入相机模块",
		value: "0x44"
	},
	{
		label: "写入星敏模块",
		value: "0x55"
	},
	{
		label: "写入电机模块user分区",
		value: "0x66"
	},
	{
		label: "写入电机模块golden分区",
		value: "0x77"
	}
]

// 软件加载选项
const loadOptions = [
	{
		label: "主控Golden版本",
		value: "0x11"
	},
	{
		label: "主控user版本",
		value: "0x22"
	},
	{
		label: "相机Golden软件",
		value: "0x33"
	},
	{
		label: "相机user软件",
		value: "0x44"
	},
	{
		label: "星敏Golden版本",
		value: "0x55"
	},
	{
		label: "星敏user软件",
		value: "0x66"
	},
	{
		label: "电机Golden软件",
		value: "0x77"
	},
	{
		label: "电机user软件",
		value: "0x88"
	},
]

// 上传的两种类型文件存放处
const spiFileList = ref([])
const binFileList = ref([])

const aLinkRef = ref(null)
const href = ref('')
const unzipped = ref(false)
const message = useMessage()


/**
 * 接收重构文件spi, 转成bin后自动发送到指定串口
 * @param file
 * @returns {Promise<void>}
 */
const handleUploadFile = async ({file}) => {
	if (!targetAddr.value) {
		message.error("未选择重构目的地址!")
		return
	}
	if (file.status === "removed") {
		return
	}
	try {
		fileLength.value = file.file.size
		// 生成数据帧
		combinedArray.value = await generateFrame(file.file, targetAddr.value)
		console.log(combinedArray.value)

		// 构造文件名称 -> 光学头1主控(0x44)20250314_140442.bin
		const label = targetAddrOptions.find(item => item.value === targetAddr.value).label
		const fileNameWithExt = `${label}${filePartition.value}(${targetAddr.value})${getTimestampFilename()}`
		// 创建一个下载链接
		href.value = URL.createObjectURL(combinedArray.value);
		aLinkRef.value.$el.download = `${fileNameWithExt}.bin`; // 设置下载文件的名称
		unzipped.value = true
		message.success("帧生成完成")
		await nextTick(() => aLinkRef.value.$el.click())

	} catch (err) {
		console.error(err);
	}
}

/**
 * 重置所有状态
 */
const reset = () => {
	combinedArray.value = null
	unzipped.value = false
	binFileList.value = []
	spiFileList.value = []

	currentTaskIndex.value = 0
	isReady.value = false
	isWriting.value = false
	isUpdating.value = false
	isLoaded.value = false
}

/**
 * 发送数据帧
 */
const sendDataFrame = async ({file = null}) => {
	if (file.status === "removed") {
		binFileList.value = []
		return
	}
	if (!refactor.value?.port?.writable) {
		binFileList.value = []
		message.error("未打开重构串口,请链接重构线并打开串口!");
		return;
	}

	let dataArray;
	if (file) {
		combinedArray.value = file.file;
		dataArray = await file.file.arrayBuffer(); // 转换为 ArrayBuffer
	} else {
		dataArray = await combinedArray.value.arrayBuffer();
	}
	// 获取重构串口线的写入器
	const writer = refactor.value.port.writable.getWriter();

	// 使用worker.js保证在后台持续运行不暂停
	const worker = new Worker(new URL("@/utils/refactor/worker.js", import.meta.url), {type: "module"});
	worker.onmessage = async (event) => {
		if (event.data.chunk) {
			try {
				await writer.write(event.data.chunk);
				// 更新进度
				percentage.value = ((event.data.count * chunkSize.value) / dataArray.byteLength * 100).toFixed(2);
			} catch (e) {
				console.error("写入失败:", e);
				worker.terminate();
				writer.releaseLock();
			}
		} else if (event.data.done) {
			writer.close();
			worker.terminate();
		}
	};

	worker.onerror = (error) => {
		console.error("Worker 错误:", error);
		writer.releaseLock();
	};

	worker.postMessage({
		data: dataArray,
		chunkSize: chunkSize.value,
		delay: delay.value
	});
};

// 一个重构任务执行中的各种状态
const currentTaskIndex = ref(0)
const isReady = ref(false)
const isWriting = ref(false)
const isUpdating = ref(false)
const isLoaded = ref(false)
</script>

<template>
	<div>
		<n-layout-content class="h-full p-2"
						  content-class="flex h-full w-full gap-4"
						  :native-scrollbar="false">
			<!--左侧操作区域-->
			<div class="flex flex-col gap-2 sm:w-3/7 xl:w-3/8">
				<n-button @click="showModal=true" size="large" secondary type="warning">自动重构点这个</n-button>
				<n-divider class="mt-1 mb-1"/>
				<n-flex class="relative p-2" align="start">
					<!--自动重构-->
					<div class="flex flex-wrap gap-1">
						<div class="w-[calc(50%-0.25rem)]">
							<n-text class="text-xl">目的地址:</n-text>
							<n-select :options="targetAddrOptions" v-model:value="targetAddr"
									  :render-label="renderLabel"
									  :on-change="(value, option) => {module=option.type}"></n-select>
						</div>
						<div class="w-[calc(50%-0.25rem)]">
							<n-text class="text-xl">分区标识:</n-text>
							<n-select :options="[{label:'user'},{label:'golden'}]"
									  v-model:value="filePartition"
									  value-field="label"
							></n-select>
						</div>
						<div class="w-[calc(50%-0.25rem)]">
							<n-text class="text-xl">写入类型:</n-text>
							<n-select :options="writeOptions" v-model:value="writeType"
							></n-select>
						</div>
						<div class="w-[calc(50%-0.25rem)]">
							<n-text class="text-xl">加载类型:</n-text>
							<n-select :options="loadOptions" v-model:value="loadType"
							></n-select>
						</div>
						<div class="w-[calc(50%-0.25rem)]">
							<n-text class="text-xl">发送延迟:</n-text>
							<n-input-number v-model:value="delay" :min="0"/>
						</div>
						<div class="w-[calc(50%-0.25rem)]">
							<n-text class="text-xl">每次发送字节数:</n-text>
							<n-input-number v-model:value="chunkSize" :min="0"/>
						</div>
						<div class="w-[calc(50%-0.25rem)]">
							<n-text class="text-xl">重构超时跳过(分钟):</n-text>
							<n-input-number v-model:value="timeout" :min="30"/>
						</div>

					</div>
					<!--两个按钮组-->
					<div class="flex gap-2">
						<n-button type="success" @click="ready(optical.port.writable)">{{
								module
							}}重构启动
						</n-button>
						<n-button type="warning" @click="stopRefactorByModule(optical.port.writable)">{{
								module
							}}重构停止
						</n-button>
						<n-button
							secondary
							type="success"
							:href="unzipped ? href : undefined"
							tag="a"
							ref="aLinkRef"
							:disabled="!unzipped">
							下载数据帧
						</n-button>

					</div>
					<div class="flex gap-2 items-center">
						<n-button @click="dataWrite(optical.port.writable)">写入</n-button>
						<n-popconfirm
							@positive-click="softwareUpdate(optical.port.writable, loadType)"
							@negative-click=";"
						>
							<template #trigger>
								<n-button>加载</n-button>
							</template>
							<template #default>确认加载
								<n-text tag="b" type="error">{{
										loadOptions.find(i => i.value === loadType).label
									}}
								</n-text>
								?
							</template>
						</n-popconfirm>
						<n-button @click="reset">重置</n-button>
					</div>
					<!--遮罩层-->
					<div v-if="isOverlay"
						 class="absolute left-0 top-0 bg-zinc-800/70 w-full h-full rounded"
						 title="仅限调试使用"></div>
				</n-flex>
				<n-flex class="flex gap-2 relative p-2 flex-nowrap">
					<!---生成spi-->
					<n-upload
						v-model:file-list="spiFileList"
						:max="1"
						:on-change="handleUploadFile"
						class="w-1/2"
						trigger-class="flex-1"
					>
						<n-upload-dragger class="h-full ps-1 pe-1 pt-14 pb-14">
							<div>
								<n-icon size="48" :depth="3">
									<ArchiveTwotone/>
								</n-icon>
							</div>
							<n-text style="font-size: 16px">
								SPI文件|生成数据帧
							</n-text>
							<n-p depth="3">
								生成前选择
								<n-p depth="3">[目的地址]和[分区标识]</n-p>
							</n-p>

						</n-upload-dragger>
					</n-upload>

					<!--上传bin-->
					<n-upload
						v-model:file-list="binFileList"
						:max="1"
						class="w-1/2"
						:on-change="sendDataFrame"
						trigger-class="flex-1"
					>
						<n-upload-dragger class="ps-1 pe-1 pt-14 pb-14 h-full">
							<div>
								<n-icon size="48" :depth="3">
									<ArchiveTwotone/>
								</n-icon>
							</div>
							<n-text style="font-size: 16px">
								BIN文件|发送数据帧
							</n-text>
							<n-p depth="3">
								发送前链接
								<n-p depth="3">重构线并打开串口</n-p>
							</n-p>
						</n-upload-dragger>
					</n-upload>
					<div v-if="isOverlay"
						 class="absolute left-0 top-0 bg-zinc-800/70 w-full h-full rounded" title="仅限调试使用"></div>
				</n-flex>
				<!--发送帧进度-->
				<n-progress type="line" processing :percentage="percentage" v-if="percentage">
					{{ Math.round((percentage / 100) * (combinedArray.size / chunkSize)) }}
					/ {{ combinedArray.size / chunkSize }}
				</n-progress>
			</div>

			<!--右侧区域-->
			<n-card class="flex flex-col w-6/8" size="huge" segmented header-class="pe-4 py-4">
				<template #header>
					注意事项:
				</template>
				<template #default>
					<n-p class="text-xl text-red-400">*.加热状态下重构有几率失败,请停掉温控!</n-p>
					<n-p class="text-xl text-red-400">*.默认star95重构前请切换至对应遥测版本-> (star95/star4)</n-p>
					<n-p class="text-xl">1.重构前请先下电再上电, 上电后无需发送任何指令!</n-p>
					<n-p class="text-xl">2.当前页面为单项重构(调试使用), 请点击左侧按钮使用自动化重构!</n-p>
					<n-p class="text-xl">3.重构上传文件顺序最好先选`相机`星敏`这些速度快的.</n-p>
					<n-p class="text-xl">4.重构时电源的电流处于
						<n-text>0.450A</n-text>
						左右才是正常现象,否则请重新操作.
					</n-p>
					<n-divider class="my-3"/>
					<n-text class="text-lg">
						<n-li>在L左侧上传spi文件可以生成帧,记得选择对应 -> [目的地址]和[分区标识]</n-li>
						<n-li>在R右侧上传bin文件可以发送帧, 记得打开重构串口</n-li>
						<n-li>[模块重构启动/停止][写入][加载][重置]这四个按钮的指令对应 ->
							[目的地址],[写入类型][加载类型]的选择
						</n-li>
					</n-text>


				</template>
				<template #action>
					<div class="flex items-center gap-2">
						<n-image
							width="40"
							title="串口链接示意图"
							src="/images/serial_example.png"
						/>
						<router-link to="doc/重构操作步骤.pdf" #="{ navigate, href }" custom>
							<n-a class="underline" :href="href" @click="navigate" target="_blank" title="重构操作指南">
								重构操作指南,点击查看
							</n-a>
						</router-link>
						<n-divider vertical class="m-2"/>
						<n-a class="underline" href="https://hexed.it/" target="_blank" title="在线查看二进制文件">
							在线查看二进制文件
						</n-a>
						<n-divider vertical class="m-2"/>
						<n-a href="http://192.168.10.100" class="underline" target="_blank"
							 title="跳转到稳定版重构程序">
							跳转到稳定版重构程序
						</n-a>
						<n-divider vertical class="m-2"/>
						<n-a href="#" @click="isOverlay=!isOverlay" title="解除遮罩层">
							Overlay
						</n-a>
					</div>

				</template>
			</n-card>


		</n-layout-content>

		<AutoModal @update:showModal="showModal=false"
				   v-model:showModal="showModal"
				   :refactor="refactor"
				   :optical="optical"
				   :notification="notification"
				   :message="message"
				   :delay="delay"
				   :latestTelemetryData="latestTelemetryData"
				   :chunkSize="chunkSize"
				   :timeout="timeout"
		/>
	</div>

</template>

<style scoped>

</style>